Split Navigation Build from Content Rendering

quarto
architecture
implementation
modular
navigation
Practical guide to implementing modular Quarto architecture with separated navigation shell and independent content builds
Author

Dario Airoldi

Published

January 30, 2025

Modified

November 3, 2025

📋 Table of Contents


📖 Overview

This document provides a step-by-step implementation guide for splitting Quarto navigation build from content rendering, based on Strategy 1: Content-Navigation Separation from the modular deployment architecture.

Prerequisites: Understanding of 001.002 Architecture - Monolithic vs. Modular Deployment.md

What You’ll Achieve:

  • Independent content builds - update content without rebuilding navigation
  • Individual page rendering - build single pages in 30-60 seconds
  • Parallel builds - multiple content sections build simultaneously
  • Faster development cycles - dramatically reduced feedback loops

Implementation Approach:

This guide shows how to transition from the current monolithic Learn repository structure to a modular architecture where navigation and content are built separately and composed at deployment time.

🎯 Implementation Strategy

Core Concept

The split architecture works by separating concerns:

Current Monolithic:
┌─────────────────────────────┐
│      Single Build Process   │
│  ┌─────────────────────┐    │
│  │Content│  Nav │Shell │    │
│  │ Pages │ Menu │Layout│    │
│  └─────────────────────┘    │
└─────────────────────────────┘

Split Architecture:
┌─────────┐  ┌──────────┐  ┌─────────┐
│ Content │  │Navigation│  │  Shell  │
│  Build  │  │  Build   │  │  Build  │
│         │  │          │  │         │
└─────────┘  └──────────┘  └─────────┘
     │            │            │
     └────────────┼────────────┘
                  │
          ┌───────────────┐
          │  Deployment   │
          │ Composition   │
          └───────────────┘

Build Process Flow

  1. Navigation Build: Creates site shell, menu structure, and API endpoints
  2. Content Builds: Generate content-only HTML pages (multiple independent builds)
  3. Composition: Merge navigation shell with content pages
  4. Deployment: Deploy composed site to target environment

Key Benefits

Aspect Before (Monolithic) After (Split)
Single page edit 2-5 minutes 30-60 seconds
Navigation change 2-5 minutes 45 seconds
Team independence Blocking Independent
Development feedback Slow Fast

🗂️ File Structure Setup

Target Directory Structure

Transform your current structure into this modular organization:

Learn-Modular/
├── navigation/                 # Navigation-only builds  
│   ├── _quarto.yml            # Shell configuration
│   ├── index.qmd              # Main landing page
│   ├── templates/
│   │   ├── shell-template.html
│   │   ├── content-wrapper.html
│   │   └── navigation-menu.html
│   ├── scripts/
│   │   ├── generate-navigation-api.ps1
│   │   ├── scan-content-structure.ps1
│   │   └── validate-links.ps1
│   ├── styles/
│   │   ├── navigation.scss
│   │   ├── shell.css
│   │   └── responsive.css
│   └── assets/
│       ├── navigation.js
│       └── search.js
├── content/                    # Content-only builds
│   ├── build-2025/
│   │   ├── _quarto.yml        # Content-specific config
│   │   ├── README.md          # Section overview
│   │   ├── sessions/
│   │   │   ├── brk101.md
│   │   │   ├── brk103.md
│   │   │   └── brk114.md
│   │   └── assets/
│   │       └── build-2025.css
│   ├── azure-topics/
│   │   ├── _quarto.yml
│   │   ├── README.md
│   │   ├── guides/
│   │   │   ├── naming-conventions.md
│   │   │   ├── storage-options.md
│   │   │   └── cosmos-access.md
│   │   └── assets/
│   └── tools/
│       ├── _quarto.yml
│       ├── README.md
│       └── guides/
├── orchestration/              # Build coordination
│   ├── build-all.ps1
│   ├── build-navigation.ps1
│   ├── build-content-section.ps1
│   ├── dev-render-page.ps1
│   ├── compose-deployment.ps1
│   ├── deploy-to-github.ps1
│   └── watch-and-rebuild.ps1
├── deploy/                     # Build outputs
│   ├── shell/                 # Navigation output
│   │   ├── index.html
│   │   ├── api/
│   │   │   ├── navigation.json
│   │   │   └── sitemap.json
│   │   └── assets/
│   ├── content/               # Content outputs
│   │   ├── build-2025/
│   │   ├── azure-topics/
│   │   └── tools/
│   └── final/                 # Composed deployment
│       ├── index.html
│       ├── content/
│       ├── api/
│       └── assets/
└── shared/                     # Common resources
    ├── templates/
    ├── styles/
    └── scripts/

Migration Steps

Step 1: Create Directory Structure

# Create new modular structure
New-Item -ItemType Directory -Path "navigation", "content", "orchestration", "deploy", "shared" -Force

# Create subdirectories
New-Item -ItemType Directory -Path "navigation/templates", "navigation/scripts", "navigation/styles", "navigation/assets" -Force
New-Item -ItemType Directory -Path "deploy/shell", "deploy/content", "deploy/final" -Force

Step 2: Move Existing Content

# Move content sections to content folder
Move-Item "202506 Build 2025" "content/build-2025"
Move-Item "20250702 Azure Naming conventions" "content/azure-topics/guides/"
Move-Item "20250827 what is yq overview" "content/tools/guides/"

# Extract navigation logic (manual process)
# Copy current _quarto.yml to navigation/_quarto.yml for modification

⚙️ Configuration Files

Content Section Configuration

Create content-only configurations for each section:

# content/build-2025/_quarto.yml
project:
  type: website
  output-dir: ../../deploy/content/build-2025
  
  # Build ALL content in this section
  render:
    - "README.md"              # Section overview
    - "sessions/*.md"          # All session files
    - "**/*.md"                # Any nested content

# Minimal format - no navigation overhead
format:
  html:
    theme: cosmo
    template-partials:
      - "../../shared/templates/content-wrapper.html"
    css:
      - "../../shared/styles/content.css"
      - "assets/build-2025.css"
    toc: true
    toc-depth: 3
    embed-resources: false
    
    # Link to shell navigation (loaded at runtime)
    include-in-header: |
      <script src="/shell/assets/navigation.js"></script>
      <link rel="stylesheet" href="/shell/assets/navigation.css">
      <meta name="content-section" content="build-2025">

# Content-specific website configuration
website:
  title: "Microsoft Build 2025 Sessions"
  description: "Notes and insights from Microsoft Build 2025 conference sessions"
  
  # NO navigation components (handled by shell)
  navbar: false
  sidebar: false
  
  # Content-specific features
  page-navigation: true
  search: false              # Delegated to shell
  reader-mode: true
  
  # Content metadata
  google-analytics: false    # Handled by shell
  cookie-consent: false      # Handled by shell

# Content-specific rendering options
execute:
  freeze: auto
  cache: true

# Cross-references within this content section
crossref:
  chapters: true
  fig-title: "Figure"
  tbl-title: "Table"
# content/azure-topics/_quarto.yml
project:
  type: website
  output-dir: ../../deploy/content/azure-topics
  
  render:
    - "README.md"
    - "guides/*.md"
    - "**/*.md"

format:
  html:
    theme: cosmo
    template-partials:
      - "../../shared/templates/content-wrapper.html"
    css:
      - "../../shared/styles/content.css"
      - "assets/azure-topics.css"
    toc: true
    toc-depth: 4
    embed-resources: false
    
    include-in-header: |
      <script src="/shell/assets/navigation.js"></script>
      <link rel="stylesheet" href="/shell/assets/navigation.css">
      <meta name="content-section" content="azure-topics">

website:
  title: "Azure Topics & Guides"
  description: "Azure architecture, services, and best practices"
  
  navbar: false
  sidebar: false
  page-navigation: true
  search: false
  reader-mode: true

🔧 Build Scripts and Orchestration

Content Section Build Script

# orchestration/build-content-section.ps1

param(
    [Parameter(Mandatory=$true)]
    [string]$Section,           # e.g., "build-2025", "azure-topics"
    
    [string]$SpecificFile,      # Optional: build only specific file
    [switch]$Watch,             # Watch for changes
    [switch]$Verbose,
    [switch]$Clean
)

function Build-ContentSection {
    param($SectionName, $SpecificFile, $WatchMode, $VerboseMode, $CleanBuild)
    
    $sectionPath = "content/$SectionName"
    
    if (-not (Test-Path $sectionPath)) {
        throw "Content section '$SectionName' not found at: $sectionPath"
    }
    
    Write-Host "🔧 Building content section: $SectionName" -ForegroundColor Cyan
    
    $startTime = Get-Date
    
    try {
        # Clean previous build if requested
        if ($CleanBuild -and (Test-Path "deploy/content/$SectionName")) {
            Remove-Item "deploy/content/$SectionName" -Recurse -Force
            Write-Host "🧹 Cleaned previous build for $SectionName" -ForegroundColor Yellow
        }
        
        # Set location to content section
        Push-Location $sectionPath
        
        # Build specific file or entire section
        if ($SpecificFile) {
            Write-Host "▶️ Building specific file: $SpecificFile" -ForegroundColor Blue
            
            if ($VerboseMode) {
                quarto render $SpecificFile --verbose
            } else {
                quarto render $SpecificFile
            }
        } else {
            Write-Host "▶️ Building entire section: $SectionName" -ForegroundColor Blue
            
            if ($VerboseMode) {
                quarto render --verbose
            } else {
                quarto render
            }
        }
        
        # Check build success
        if ($LASTEXITCODE -eq 0) {
            $endTime = Get-Date
            $duration = ($endTime - $startTime).TotalSeconds
            Write-Host "✅ Content section '$SectionName' built successfully in $([math]::Round($duration, 2)) seconds" -ForegroundColor Green
            
            # Validate output
            $outputPath = "../../deploy/content/$SectionName"
            if (Test-Path $outputPath) {
                $fileCount = (Get-ChildItem $outputPath -Recurse -File).Count
                Write-Host "✅ Generated $fileCount files in $outputPath" -ForegroundColor Green
            }
        } else {
            throw "Quarto render failed with exit code: $LASTEXITCODE"
        }
        
    } catch {
        Write-Host "❌ Content build failed: $($_.Exception.Message)" -ForegroundColor Red
        exit 1
    } finally {
        Pop-Location
    }
}

function Start-WatchMode {
    param($SectionName)
    
    Write-Host "👀 Starting watch mode for section: $SectionName" -ForegroundColor Magenta
    Write-Host "   Press Ctrl+C to stop watching..." -ForegroundColor Gray
    
    $watcher = New-Object System.IO.FileSystemWatcher
    $watcher.Path = (Resolve-Path "content/$SectionName").Path
    $watcher.Filter = "*.md"
    $watcher.IncludeSubdirectories = $true
    $watcher.NotifyFilter = [System.IO.NotifyFilters]::LastWrite
    
    $action = {
        $path = $Event.SourceEventArgs.FullPath
        $name = $Event.SourceEventArgs.Name
        $changeType = $Event.SourceEventArgs.ChangeType
        
        Write-Host "📝 File changed: $name" -ForegroundColor Yellow
        
        # Get relative path for quarto render
        $relativePath = $path.Replace((Resolve-Path "content/$SectionName").Path + "\", "").Replace("\", "/")
        
        # Rebuild the specific file
        Build-ContentSection -SectionName $SectionName -SpecificFile $relativePath -VerboseMode $false -CleanBuild $false
    }
    
    Register-ObjectEvent -InputObject $watcher -EventName "Changed" -Action $action
    
    $watcher.EnableRaisingEvents = $true
    
    try {
        # Keep the script running
        while ($true) {
            Start-Sleep 1
        }
    } finally {
        $watcher.Dispose()
    }
}

# Execute build
if ($Watch) {
    Start-WatchMode -SectionName $Section
} else {
    Build-ContentSection -SectionName $Section -SpecificFile $SpecificFile -VerboseMode $Verbose -CleanBuild $Clean
}

Individual Page Build Script

# orchestration/dev-render-page.ps1

param(
    [Parameter(Mandatory=$true)]
    [string]$PagePath,          # e.g., "content/build-2025/sessions/brk101.md"
    
    [switch]$Watch,             # Auto-rebuild on changes
    [switch]$Verbose,
    [switch]$OpenInBrowser
)

function Render-SinglePage {
    param($FilePath, $VerboseMode)
    
    if (-not (Test-Path $FilePath)) {
        throw "File not found: $FilePath"
    }
    
    Write-Host "🔄 Rendering single page: $FilePath" -ForegroundColor Cyan
    
    $startTime = Get-Date
    
    try {
        # Extract content section from path
        $pathParts = $FilePath.Split([IO.Path]::DirectorySeparatorChar)
        $sectionIndex = [Array]::IndexOf($pathParts, "content") + 1
        $section = $pathParts[$sectionIndex]
        
        if (-not $section) {
            throw "Cannot determine content section from path: $FilePath"
        }
        
        # Get section directory and config
        $sectionDir = "content/$section"
        $configFile = "$sectionDir/_quarto.yml"
        
        if (-not (Test-Path $configFile)) {
            throw "Section config not found: $configFile"
        }
        
        # Get relative path within section
        $relativePath = $FilePath.Replace("content/$section/", "").Replace("\", "/")
        
        Write-Host "   Section: $section" -ForegroundColor Gray
        Write-Host "   File: $relativePath" -ForegroundColor Gray
        
        # Set location to section directory
        Push-Location $sectionDir
        
        # Render the specific file
        if ($VerboseMode) {
            quarto render $relativePath --verbose
        } else {
            quarto render $relativePath
        }
        
        if ($LASTEXITCODE -eq 0) {
            $endTime = Get-Date
            $duration = ($endTime - $startTime).TotalSeconds
            Write-Host "✅ Page rendered successfully in $([math]::Round($duration, 2)) seconds" -ForegroundColor Green
            
            # Determine output file path
            $outputFile = $relativePath.Replace(".md", ".html").Replace(".qmd", ".html")
            $fullOutputPath = "../../deploy/content/$section/$outputFile"
            
            if (Test-Path $fullOutputPath) {
                Write-Host "✅ Output: $fullOutputPath" -ForegroundColor Green
                
                if ($OpenInBrowser) {
                    $absolutePath = (Resolve-Path $fullOutputPath).Path
                    Start-Process $absolutePath
                    Write-Host "🌐 Opened in browser" -ForegroundColor Blue
                }
            }
        } else {
            throw "Quarto render failed with exit code: $LASTEXITCODE"
        }
        
    } catch {
        Write-Host "❌ Page render failed: $($_.Exception.Message)" -ForegroundColor Red
        exit 1
    } finally {
        Pop-Location
    }
}

function Start-PageWatchMode {
    param($FilePath)
    
    $fullPath = (Resolve-Path $FilePath).Path
    $directory = Split-Path $fullPath
    $fileName = Split-Path $fullPath -Leaf
    
    Write-Host "👀 Watching file: $fileName" -ForegroundColor Magenta
    Write-Host "   Directory: $directory" -ForegroundColor Gray
    Write-Host "   Press Ctrl+C to stop watching..." -ForegroundColor Gray
    
    $watcher = New-Object System.IO.FileSystemWatcher
    $watcher.Path = $directory
    $watcher.Filter = $fileName
    $watcher.NotifyFilter = [System.IO.NotifyFilters]::LastWrite
    
    $action = {
        Write-Host "`n📝 File changed, rebuilding..." -ForegroundColor Yellow
        Render-SinglePage -FilePath $PagePath -VerboseMode $Verbose
    }
    
    Register-ObjectEvent -InputObject $watcher -EventName "Changed" -Action $action
    
    $watcher.EnableRaisingEvents = $true
    
    try {
        # Initial build
        Render-SinglePage -FilePath $PagePath -VerboseMode $Verbose
        
        # Keep watching
        while ($true) {
            Start-Sleep 1
        }
    } finally {
        $watcher.Dispose()
    }
}

# Execute
if ($Watch) {
    Start-PageWatchMode -FilePath $PagePath
} else {
    Render-SinglePage -FilePath $PagePath -VerboseMode $Verbose
}

Deployment Composition Script

# orchestration/compose-deployment.ps1

param(
    [string[]]$ContentSections = @(),  # Specific sections to include, empty = all
    [switch]$Force,                    # Force rebuild of final deployment
    [switch]$Verbose
)

function Compose-FinalDeployment {
    param($Sections, $ForceRebuild, $VerboseMode)
    
    Write-Host "🔧 Composing final deployment..." -ForegroundColor Cyan
    
    $startTime = Get-Date
    
    try {
        # Clean final deployment if forced
        if ($ForceRebuild -and (Test-Path "deploy/final")) {
            Remove-Item "deploy/final" -Recurse -Force
            Write-Host "🧹 Cleaned previous final deployment" -ForegroundColor Yellow
        }
        
        # Ensure final directory exists
        if (-not (Test-Path "deploy/final")) {
            New-Item -ItemType Directory -Path "deploy/final" -Force | Out-Null
        }
        
        # Copy navigation shell
        Write-Host "📋 Copying navigation shell..." -ForegroundColor Blue
        if (Test-Path "deploy/shell") {
            Copy-Item "deploy/shell/*" "deploy/final/" -Recurse -Force
            Write-Host "✅ Navigation shell copied" -ForegroundColor Green
        } else {
            throw "Navigation shell not found. Run build-navigation.ps1 first."
        }
        
        # Determine content sections to include
        if ($Sections.Count -eq 0) {
            # Include all available content sections
            $Sections = Get-ChildItem "deploy/content" -Directory | ForEach-Object { $_.Name }
            Write-Host "📂 Including all content sections: $($Sections -join ', ')" -ForegroundColor Gray
        } else {
            Write-Host "📂 Including specific sections: $($Sections -join ', ')" -ForegroundColor Gray
        }
        
        # Copy content sections
        Write-Host "📋 Copying content sections..." -ForegroundColor Blue
        
        # Ensure content directory exists in final
        if (-not (Test-Path "deploy/final/content")) {
            New-Item -ItemType Directory -Path "deploy/final/content" -Force | Out-Null
        }
        
        foreach ($section in $Sections) {
            $sourcePath = "deploy/content/$section"
            $targetPath = "deploy/final/content/$section"
            
            if (Test-Path $sourcePath) {
                Copy-Item $sourcePath $targetPath -Recurse -Force
                
                $fileCount = (Get-ChildItem $sourcePath -Recurse -File).Count
                Write-Host "  ✅ $section ($fileCount files)" -ForegroundColor Green
            } else {
                Write-Host "  ⚠️ $section (not found - skipping)" -ForegroundColor Yellow
            }
        }
        
        # Generate final sitemap
        Write-Host "🗺️ Generating final sitemap..." -ForegroundColor Blue
        & "orchestration/generate-final-sitemap.ps1" -DeployPath "deploy/final"
        
        # Validate final deployment
        Write-Host "🔍 Validating final deployment..." -ForegroundColor Blue
        $totalFiles = (Get-ChildItem "deploy/final" -Recurse -File).Count
        $htmlFiles = (Get-ChildItem "deploy/final" -Recurse -Filter "*.html").Count
        
        Write-Host "📊 Final deployment statistics:" -ForegroundColor Cyan
        Write-Host "   Total files: $totalFiles" -ForegroundColor Gray
        Write-Host "   HTML pages: $htmlFiles" -ForegroundColor Gray
        Write-Host "   Content sections: $($Sections.Count)" -ForegroundColor Gray
        
        # Check for required files
        $requiredFiles = @("index.html", "api/navigation.json")
        foreach ($file in $requiredFiles) {
            if (Test-Path "deploy/final/$file") {
                Write-Host "   ✅ $file" -ForegroundColor Green
            } else {
                Write-Host "   ❌ $file (missing)" -ForegroundColor Red
            }
        }
        
        $endTime = Get-Date
        $duration = ($endTime - $startTime).TotalSeconds
        Write-Host "✅ Final deployment composed successfully in $([math]::Round($duration, 2)) seconds" -ForegroundColor Green
        Write-Host "🚀 Ready for deployment from: deploy/final/" -ForegroundColor Cyan
        
    } catch {
        Write-Host "❌ Deployment composition failed: $($_.Exception.Message)" -ForegroundColor Red
        exit 1
    }
}

# Execute composition
Compose-FinalDeployment -Sections $ContentSections -ForceRebuild $Force -VerboseMode $Verbose

🚀 Development Workflow

Daily Development Commands

Build Everything (Full Site)

# Build complete site from scratch
.\orchestration\build-all.ps1 -Clean

Build Navigation Only

# Update navigation/menu structure
.\orchestration\build-navigation.ps1 -Clean

Build Specific Content Section

# Work on Build 2025 content
.\orchestration\build-content-section.ps1 -Section "build-2025"

# Work on Azure topics
.\orchestration\build-content-section.ps1 -Section "azure-topics" -Verbose

Build Individual Page

# Edit single page with fast feedback
.\orchestration\dev-render-page.ps1 -PagePath "content/build-2025/sessions/brk101.md"

# Watch mode for continuous development
.\orchestration\dev-render-page.ps1 -PagePath "content/build-2025/sessions/brk101.md" -Watch

Compose and Deploy

# Compose final site and deploy
.\orchestration\compose-deployment.ps1
.\orchestration\deploy-to-github.ps1

Development Scenarios

Scenario 1: Editing Build 2025 Content

# Start watching Build 2025 section
.\orchestration\build-content-section.ps1 -Section "build-2025" -Watch

# In another terminal, edit specific pages with immediate feedback
.\orchestration\dev-render-page.ps1 -PagePath "content/build-2025/sessions/brk101.md" -Watch -OpenInBrowser

Scenario 2: Adding New Content Section

# 1. Create directory structure
New-Item -ItemType Directory -Path "content/new-section/guides" -Force

# 2. Copy template configuration
Copy-Item "content/azure-topics/_quarto.yml" "content/new-section/_quarto.yml"

# 3. Update navigation to include new section
# Edit navigation/_quarto.yml manually

# 4. Test new section
.\orchestration\build-content-section.ps1 -Section "new-section"
.\orchestration\build-navigation.ps1
.\orchestration\compose-deployment.ps1

Scenario 3: Team Collaboration

# Team member working on Azure content
.\orchestration\build-content-section.ps1 -Section "azure-topics" -Watch

# Another team member working on Tools
.\orchestration\build-content-section.ps1 -Section "tools" -Watch

# Independent work - no build conflicts!

Performance Comparison

Task Monolithic Split Architecture
Edit single page 2-5 minutes 30-60 seconds
Add new page 2-5 minutes 45 seconds
Update navigation 2-5 minutes 45 seconds
Section rebuild 5-10 minutes 1-2 minutes
Full site rebuild 5-10 minutes 3-4 minutes (parallel)

🔄 Migration Process

Phase 1: Preparation (Week 1)

Day 1-2: Analysis and Planning

# Analyze current content structure
Get-ChildItem -Recurse -Filter "*.md" | Group-Object Directory | Sort-Object Count -Descending

# Identify content sections
$contentSections = @("build-2025", "azure-topics", "tools", "issues-solutions")

# Plan migration order (start with least critical)
$migrationOrder = @("tools", "azure-topics", "build-2025", "issues-solutions")

Day 3-5: Setup Infrastructure

# Create modular directory structure
.\migration\setup-modular-structure.ps1

# Create template configurations
.\migration\create-template-configs.ps1

# Setup build scripts
.\migration\setup-build-scripts.ps1

Phase 2: Migration (Week 2-3)

Content Migration Script

# migration/migrate-content-section.ps1

param(
    [Parameter(Mandatory=$true)]
    [string]$SectionName,
    
    [Parameter(Mandatory=$true)]
    [string]$SourcePath,
    
    [switch]$DryRun
)

function Migrate-ContentSection {
    param($Section, $Source, $TestRun)
    
    Write-Host "🔄 Migrating content section: $Section" -ForegroundColor Cyan
    
    $targetPath = "content/$Section"
    
    if ($TestRun) {
        Write-Host "🧪 DRY RUN - No files will be moved" -ForegroundColor Yellow
        Write-Host "   Source: $Source" -ForegroundColor Gray
        Write-Host "   Target: $targetPath" -ForegroundColor Gray
        return
    }
    
    try {
        # Create target directory
        if (-not (Test-Path $targetPath)) {
            New-Item -ItemType Directory -Path $targetPath -Force
        }
        
        # Move content files
        Move-Item "$Source/*" $targetPath -Force
        
        # Create section-specific config
        Copy-Item "templates/_quarto-content-template.yml" "$targetPath/_quarto.yml"
        
        # Update config for this section
        $config = Get-Content "$targetPath/_quarto.yml"
        $config = $config -replace "SECTION_NAME", $Section
        $config = $config -replace "SECTION_TITLE", (Get-Culture).TextInfo.ToTitleCase($Section -replace "-", " ")
        Set-Content "$targetPath/_quarto.yml" $config
        
        Write-Host "✅ Migration completed: $Section" -ForegroundColor Green
        
        # Test build
        Write-Host "🧪 Testing build..." -ForegroundColor Blue
        .\orchestration\build-content-section.ps1 -Section $Section
        
    } catch {
        Write-Host "❌ Migration failed: $($_.Exception.Message)" -ForegroundColor Red
        throw
    }
}

Migrate-ContentSection -Section $SectionName -Source $SourcePath -TestRun $DryRun

Navigation Migration

# migration/migrate-navigation.ps1

function Migrate-Navigation {
    Write-Host "🔄 Migrating navigation configuration..." -ForegroundColor Cyan
    
    try {
        # Backup current _quarto.yml
        Copy-Item "_quarto.yml" "_quarto.yml.backup"
        
        # Extract navigation parts
        $currentConfig = Get-Content "_quarto.yml" | ConvertFrom-Yaml
        
        # Create navigation config
        $navConfig = @{
            project = @{
                type = "website"
                "output-dir" = "../deploy/shell"
            }
            website = $currentConfig.website
            format = $currentConfig.format
        }
        
        # Remove content rendering from navigation
        $navConfig.project.render = @("index.qmd", "search.qmd", "about.qmd")
        $navConfig.website.navbar = $false
        $navConfig.website.sidebar = $false
        
        # Save navigation config
        $navConfig | ConvertTo-Yaml | Set-Content "navigation/_quarto.yml"
        
        Write-Host "✅ Navigation configuration migrated" -ForegroundColor Green
        
        # Test navigation build
        .\orchestration\build-navigation.ps1
        
    } catch {
        Write-Host "❌ Navigation migration failed: $($_.Exception.Message)" -ForegroundColor Red
        throw
    }
}

Migrate-Navigation

Phase 3: Testing and Validation (Week 4)

Validation Script

# migration/validate-migration.ps1

function Test-ModularArchitecture {
    Write-Host "🧪 Validating modular architecture..." -ForegroundColor Cyan
    
    $tests = @()
    
    # Test 1: Navigation build
    try {
        .\orchestration\build-navigation.ps1
        $tests += @{ Name = "Navigation Build"; Status = "PASS" }
    } catch {
        $tests += @{ Name = "Navigation Build"; Status = "FAIL"; Error = $_.Exception.Message }
    }
    
    # Test 2: Content section builds
    $sections = Get-ChildItem "content" -Directory
    foreach ($section in $sections) {
        try {
            .\orchestration\build-content-section.ps1 -Section $section.Name
            $tests += @{ Name = "Content: $($section.Name)"; Status = "PASS" }
        } catch {
            $tests += @{ Name = "Content: $($section.Name)"; Status = "FAIL"; Error = $_.Exception.Message }
        }
    }
    
    # Test 3: Individual page builds
    $testPages = @(
        "content/build-2025/sessions/brk101.md",
        "content/azure-topics/guides/naming-conventions.md"
    )
    
    foreach ($page in $testPages) {
        if (Test-Path $page) {
            try {
                .\orchestration\dev-render-page.ps1 -PagePath $page
                $tests += @{ Name = "Page: $(Split-Path $page -Leaf)"; Status = "PASS" }
            } catch {
                $tests += @{ Name = "Page: $(Split-Path $page -Leaf)"; Status = "FAIL"; Error = $_.Exception.Message }
            }
        }
    }
    
    # Test 4: Final composition
    try {
        .\orchestration\compose-deployment.ps1
        $tests += @{ Name = "Final Composition"; Status = "PASS" }
    } catch {
        $tests += @{ Name = "Final Composition"; Status = "FAIL"; Error = $_.Exception.Message }
    }
    
    # Report results
    Write-Host "`n📊 Test Results:" -ForegroundColor Cyan
    foreach ($test in $tests) {
        if ($test.Status -eq "PASS") {
            Write-Host "  ✅ $($test.Name)" -ForegroundColor Green
        } else {
            Write-Host "  ❌ $($test.Name): $($test.Error)" -ForegroundColor Red
        }
    }
    
    $passCount = ($tests | Where-Object { $_.Status -eq "PASS" }).Count
    $totalCount = $tests.Count
    
    Write-Host "`n📈 Summary: $passCount/$totalCount tests passed" -ForegroundColor Cyan
    
    if ($passCount -eq $totalCount) {
        Write-Host "🎉 Migration validation successful!" -ForegroundColor Green
    } else {
        Write-Host "⚠️ Migration validation failed - review errors above" -ForegroundColor Yellow
    }
}

Test-ModularArchitecture

🧪 Testing and Validation

Automated Testing Suite

Create comprehensive tests to ensure the split architecture works correctly:

# tests/test-split-architecture.ps1

function Test-BuildPerformance {
    Write-Host "⏱️ Testing build performance..." -ForegroundColor Cyan
    
    # Test individual page build time
    $testPage = "content/build-2025/sessions/brk101.md"
    if (Test-Path $testPage) {
        $startTime = Get-Date
        .\orchestration\dev-render-page.ps1 -PagePath $testPage
        $pageTime = (Get-Date) - $startTime
        
        Write-Host "📊 Individual page build: $([math]::Round($pageTime.TotalSeconds, 2)) seconds" -ForegroundColor Green
    }
    
    # Test section build time
    $startTime = Get-Date
    .\orchestration\build-content-section.ps1 -Section "azure-topics"
    $sectionTime = (Get-Date) - $startTime
    
    Write-Host "📊 Section build time: $([math]::Round($sectionTime.TotalSeconds, 2)) seconds" -ForegroundColor Green
    
    # Test navigation build time
    $startTime = Get-Date
    .\orchestration\build-navigation.ps1
    $navTime = (Get-Date) - $startTime
    
    Write-Host "📊 Navigation build time: $([math]::Round($navTime.TotalSeconds, 2)) seconds" -ForegroundColor Green
}

function Test-LinkIntegrity {
    Write-Host "🔗 Testing link integrity..." -ForegroundColor Cyan
    
    # Check navigation links point to existing content
    $navConfig = Get-Content "navigation/_quarto.yml" | ConvertFrom-Yaml
    $links = @()
    
    # Extract links from navigation (simplified)
    # In practice, you'd parse the full navigation structure
    
    foreach ($link in $links) {
        $targetFile = "deploy/final$link"
        if (Test-Path $targetFile) {
            Write-Host "  ✅ $link" -ForegroundColor Green
        } else {
            Write-Host "  ❌ $link (broken)" -ForegroundColor Red
        }
    }
}

function Test-ContentIntegrity {
    Write-Host "📄 Testing content integrity..." -ForegroundColor Cyan
    
    # Check all content sections have valid output
    $sections = Get-ChildItem "content" -Directory
    
    foreach ($section in $sections) {
        $outputPath = "deploy/content/$($section.Name)"
        if (Test-Path $outputPath) {
            $htmlFiles = Get-ChildItem $outputPath -Recurse -Filter "*.html"
            Write-Host "  ✅ $($section.Name): $($htmlFiles.Count) pages" -ForegroundColor Green
        } else {
            Write-Host "  ❌ $($section.Name): no output found" -ForegroundColor Red
        }
    }
}

# Run all tests
Test-BuildPerformance
Test-LinkIntegrity  
Test-ContentIntegrity

Visual Validation

# tests/visual-validation.ps1

function Start-LocalPreview {
    Write-Host "🌐 Starting local preview server..." -ForegroundColor Cyan
    
    # Ensure final deployment exists
    if (-not (Test-Path "deploy/final/index.html")) {
        Write-Host "🔧 Building final deployment..." -ForegroundColor Blue
        .\orchestration\compose-deployment.ps1
    }
    
    # Start simple HTTP server
    Push-Location "deploy/final"
    
    try {
        # Use Python HTTP server if available
        if (Get-Command python -ErrorAction SilentlyContinue) {
            Write-Host "▶️ Starting Python HTTP server on http://localhost:8000" -ForegroundColor Green
            python -m http.server 8000
        } 
        # Or use PowerShell-based server
        else {
            Write-Host "▶️ Starting PowerShell HTTP server on http://localhost:8080" -ForegroundColor Green
            & "$PSScriptRoot\simple-http-server.ps1" -Port 8080
        }
    } finally {
        Pop-Location
    }
}

function Test-CrossSectionNavigation {
    Write-Host "🧭 Testing cross-section navigation..." -ForegroundColor Cyan
    
    # This would typically be done with browser automation
    # For now, provide manual testing checklist
    
    $testCases = @(
        "Navigate from home to Build 2025 section",
        "Navigate between different Build 2025 sessions",
        "Navigate from Build 2025 to Azure topics",
        "Use search functionality",
        "Test responsive design on mobile",
        "Verify all internal links work",
        "Check external links open correctly"
    )
    
    Write-Host "📋 Manual testing checklist:" -ForegroundColor Yellow
    foreach ($test in $testCases) {
        Write-Host "  ☐ $test" -ForegroundColor Gray
    }
    
    Write-Host "`n🌐 Open http://localhost:8000 to begin testing" -ForegroundColor Cyan
}

Start-LocalPreview
Test-CrossSectionNavigation

📊 Performance Comparison

Build Time Measurements

Create benchmarking tools to measure the performance improvements:

# benchmarks/measure-build-performance.ps1

function Measure-BuildTimes {
    Write-Host "📊 Measuring build performance..." -ForegroundColor Cyan
    
    $results = @()
    
    # Measure individual page build
    $testPage = "content/build-2025/sessions/brk101.md"
    if (Test-Path $testPage) {
        $times = @()
        for ($i = 1; $i -le 5; $i++) {
            Write-Host "🔄 Individual page build - Run $i/5" -ForegroundColor Blue
            $startTime = Get-Date
            .\orchestration\dev-render-page.ps1 -PagePath $testPage | Out-Null
            $endTime = Get-Date
            $times += ($endTime - $startTime).TotalSeconds
        }
        $avgTime = ($times | Measure-Object -Average).Average
        $results += @{ Task = "Individual Page"; Time = $avgTime; Unit = "seconds" }
    }
    
    # Measure section build
    $times = @()
    for ($i = 1; $i -le 3; $i++) {
        Write-Host "🔄 Section build - Run $i/3" -ForegroundColor Blue
        $startTime = Get-Date
        .\orchestration\build-content-section.ps1 -Section "azure-topics" | Out-Null
        $endTime = Get-Date
        $times += ($endTime - $startTime).TotalSeconds
    }
    $avgTime = ($times | Measure-Object -Average).Average
    $results += @{ Task = "Section Build"; Time = $avgTime; Unit = "seconds" }
    
    # Measure navigation build
    $times = @()
    for ($i = 1; $i -le 3; $i++) {
        Write-Host "🔄 Navigation build - Run $i/3" -ForegroundColor Blue
        $startTime = Get-Date
        .\orchestration\build-navigation.ps1 | Out-Null
        $endTime = Get-Date
        $times += ($endTime - $startTime).TotalSeconds
    }
    $avgTime = ($times | Measure-Object -Average).Average
    $results += @{ Task = "Navigation Build"; Time = $avgTime; Unit = "seconds" }
    
    # Measure full composition
    $startTime = Get-Date
    .\orchestration\compose-deployment.ps1 | Out-Null
    $endTime = Get-Date
    $composeTime = ($endTime - $startTime).TotalSeconds
    $results += @{ Task = "Final Composition"; Time = $composeTime; Unit = "seconds" }
    
    # Display results
    Write-Host "`n📊 Performance Results:" -ForegroundColor Cyan
    $results | ForEach-Object {
        Write-Host "  $($_.Task): $([math]::Round($_.Time, 2)) $($_.Unit)" -ForegroundColor Green
    }
    
    # Compare with estimated monolithic times
    Write-Host "`n📈 Comparison with Monolithic Approach:" -ForegroundColor Cyan
    Write-Host "  Individual Page: ~180 seconds (monolithic) vs $([math]::Round($results[0].Time, 2)) seconds (split)" -ForegroundColor Yellow
    Write-Host "  Navigation Update: ~180 seconds (monolithic) vs $([math]::Round($results[2].Time, 2)) seconds (split)" -ForegroundColor Yellow
    
    $improvement = ((180 - $results[0].Time) / 180) * 100
    Write-Host "  Improvement: $([math]::Round($improvement, 1))% faster" -ForegroundColor Green
}

Measure-BuildTimes

🛠️ Troubleshooting

Common Issues and Solutions

Issue 1: Navigation Links Not Working

# Fix navigation links pointing to wrong locations
function Fix-NavigationLinks {
    Write-Host "🔧 Fixing navigation links..." -ForegroundColor Cyan
    
    # Check navigation config
    $navConfig = "navigation/_quarto.yml"
    if (Test-Path $navConfig) {
        $content = Get-Content $navConfig
        
        # Look for incorrect paths
        $incorrectPaths = $content | Where-Object { $_ -match 'href:.*content.*\.md' }
        
        if ($incorrectPaths) {
            Write-Host "❌ Found incorrect paths in navigation:" -ForegroundColor Red
            $incorrectPaths | ForEach-Object { Write-Host "  $_" -ForegroundColor Gray }
            
            Write-Host "💡 Navigation should point to .html files in /content/ paths" -ForegroundColor Yellow
        } else {
            Write-Host "✅ Navigation paths look correct" -ForegroundColor Green
        }
    }
}

Issue 2: Content Not Found

function Diagnose-ContentIssues {
    Write-Host "🔍 Diagnosing content issues..." -ForegroundColor Cyan
    
    # Check content section configs
    $sections = Get-ChildItem "content" -Directory
    
    foreach ($section in $sections) {
        $configFile = "$($section.FullName)/_quarto.yml"
        $outputDir = "deploy/content/$($section.Name)"
        
        Write-Host "`n📂 Section: $($section.Name)" -ForegroundColor Blue
        
        if (-not (Test-Path $configFile)) {
            Write-Host "  ❌ Missing _quarto.yml config" -ForegroundColor Red
        } else {
            Write-Host "  ✅ Config file exists" -ForegroundColor Green
        }
        
        if (-not (Test-Path $outputDir)) {
            Write-Host "  ❌ No build output found" -ForegroundColor Red
            Write-Host "  💡 Run: .\orchestration\build-content-section.ps1 -Section '$($section.Name)'" -ForegroundColor Yellow
        } else {
            $htmlCount = (Get-ChildItem $outputDir -Recurse -Filter "*.html").Count
            Write-Host "  ✅ Build output: $htmlCount HTML files" -ForegroundColor Green
        }
    }
}

Issue 3: Slow Build Performance

function Optimize-BuildPerformance {
    Write-Host "⚡ Optimizing build performance..." -ForegroundColor Cyan
    
    # Check for large files that might slow builds
    $largeFiles = Get-ChildItem "content" -Recurse -File | Where-Object { $_.Length -gt 1MB }
    
    if ($largeFiles) {
        Write-Host "⚠️ Large files found (>1MB):" -ForegroundColor Yellow
        $largeFiles | ForEach-Object {
            $sizeMB = [math]::Round($_.Length / 1MB, 2)
            Write-Host "  $($_.FullName) ($sizeMB MB)" -ForegroundColor Gray
        }
        Write-Host "💡 Consider optimizing images or splitting large documents" -ForegroundColor Yellow
    }
    
    # Check for excessive file counts
    $sections = Get-ChildItem "content" -Directory
    foreach ($section in $sections) {
        $fileCount = (Get-ChildItem $section.FullName -Recurse -Filter "*.md").Count
        if ($fileCount -gt 50) {
            Write-Host "⚠️ Section '$($section.Name)' has $fileCount files - consider splitting" -ForegroundColor Yellow
        }
    }
    
    # Suggest optimizations
    Write-Host "`n💡 Performance Tips:" -ForegroundColor Cyan
    Write-Host "  • Use -Clean flag only when necessary" -ForegroundColor Gray
    Write-Host "  • Build individual sections instead of full site during development" -ForegroundColor Gray
    Write-Host "  • Use watch mode for continuous development" -ForegroundColor Gray
    Write-Host "  • Consider excluding large assets from frequent builds" -ForegroundColor Gray
}

Debugging Tools

# debug/debug-build-process.ps1

function Debug-BuildProcess {
    param(
        [string]$Section,
        [string]$PagePath
    )
    
    Write-Host "🐛 Debugging build process..." -ForegroundColor Cyan
    
    if ($PagePath) {
        # Debug individual page build
        Write-Host "🔍 Debugging page: $PagePath" -ForegroundColor Blue
        
        # Check file exists
        if (-not (Test-Path $PagePath)) {
            Write-Host "❌ File not found: $PagePath" -ForegroundColor Red
            return
        }
        
        # Check section config
        $pathParts = $PagePath.Split([IO.Path]::DirectorySeparatorChar)
        $sectionIndex = [Array]::IndexOf($pathParts, "content") + 1
        $section = $pathParts[$sectionIndex]
        $configPath = "content/$section/_quarto.yml"
        
        if (-not (Test-Path $configPath)) {
            Write-Host "❌ Section config not found: $configPath" -ForegroundColor Red
            return
        }
        
        Write-Host "✅ Section: $section" -ForegroundColor Green
        Write-Host "✅ Config: $configPath" -ForegroundColor Green
        
        # Run build with verbose output
        Write-Host "🔄 Running verbose build..." -ForegroundColor Blue
        .\orchestration\dev-render-page.ps1 -PagePath $PagePath -Verbose
        
    } elseif ($Section) {
        # Debug section build
        Write-Host "🔍 Debugging section: $Section" -ForegroundColor Blue
        
        $sectionPath = "content/$Section"
        $configPath = "$sectionPath/_quarto.yml"
        
        if (-not (Test-Path $sectionPath)) {
            Write-Host "❌ Section not found: $sectionPath" -ForegroundColor Red
            return
        }
        
        if (-not (Test-Path $configPath)) {
            Write-Host "❌ Section config not found: $configPath" -ForegroundColor Red
            return
        }
        
        # Show config details
        Write-Host "📄 Section configuration:" -ForegroundColor Blue
        Get-Content $configPath | Write-Host -ForegroundColor Gray
        
        # Run build with verbose output
        Write-Host "🔄 Running verbose build..." -ForegroundColor Blue
        .\orchestration\build-content-section.ps1 -Section $Section -Verbose
    }
}

# Example usage:
# Debug-BuildProcess -PagePath "content/build-2025/sessions/brk101.md"
# Debug-BuildProcess -Section "azure-topics"

Summary

This implementation guide provides a complete roadmap for splitting Quarto navigation build from content rendering. The modular approach offers:

  • 60-90% faster individual page builds
  • Independent team workflows without blocking
  • Parallel development of different content sections
  • Scalable architecture that grows with your content

Next Steps:

  1. Start small: Migrate one content section first
  2. Test thoroughly: Use the provided validation scripts
  3. Measure performance: Document the improvements
  4. Iterate: Refine the process based on your specific needs

The split architecture transforms Quarto from a monolithic site generator into a modular, scalable documentation platform perfect for growing teams and content volumes! 🚀